let aiDepth = 0
let concealTiles = 14
let initialDora = 1
let maxDora = 5
let playerCount = 4
let maxHu = 1
let maxKan = 4
let akaCount = [1, 1, 1, 0]
let playing = [true, false, false, false]

const TENPAI_GAIN = [0, 3000, 1500, 1000, 0]
const NOTEN_LOSS = [0, 1000, 1500, 3000, 0]

function createRound(name, dealer, render, end) {
	// Generate pile
	let pile = []
	for (let suit = 0; suit < 4; ++suit) {
		for (let tile = (suit === 3 ? 31 : (36 | suit)); tile > 3; tile -= 4) {
			for (let i = 0; i < 4; ++i) {
				pile.push((tile === (5 << 2 | suit) && i < akaCount[suit]) ? (tile | TILE_AKA) : tile)
			}
		}
	}
	shuffleArray(pile)

	let wanpai = []
	for (let i = 0; i < concealTiles; ++i) {
		wanpai.push(pile.pop())
	}

	// Initialize player information
	let players = new Array(playerCount)
	for (let playerId = 0; playerId < playerCount; ++playerId) {
		let tiles = []
		for (let i = 0; i < 13; ++i) {
			tiles.push(pile.pop())
		}
		tiles.sort(compareTiles)

		players[playerId] = {
			hand: {
				melds: [],
				tiles: tiles,
				furitenTable: new Array(39).fill(0),
			},
			discard: [],
			gain: 0,
			loss: 0,
			dealIn: [],
		}
	}

	// Compose round
	return {
		end: end,
		render: render,
		ended: false,
		name: name,
		pile: pile,
		wanpai: wanpai,
		doraCount: initialDora,
		players: players,
		toPlay: dealer,
		phase: PHASE_DRAW,
		remainingKan: maxKan,
		suufonCount: 0,
		suufonTile: 0,
	}
}

function getNextPlayer(round, playerId) {
	for (let i = (playerId + 1) % playerCount; i != playerId; i = (i + 1) % playerCount) {
		if (!round.players[i].hu)
			return i
	}
	return playerId
}

function endRound(round, tochuuRyuukyoku) {
	if (tochuuRyuukyoku) {
		round.tochuuRyuukyoku = tochuuRyuukyoku
		for (let playerId = 0; playerId < playerCount; ++playerId) {
			round.players[playerId].gain = 0
		}
	} else {
		let noHu = true
		for (let playerId = 0; playerId < playerCount; ++playerId) {
			let player = round.players[playerId]
			if (player.hu) {
				noHu = false
				if (playerId !== round.dealer && !round.players[round.dealer].hu) {
					round.progress = true
				}
			}
		}
		if (noHu) {
			let tenpaiCount = 0
			for (let playerId = 0; playerId < playerCount; ++playerId) {
				let player = round.players[playerId]
				player.tenpai = tenpai(player.hand)
				if (player.tenpai) {
					++tenpaiCount
				}
			}
			const tenpaiGain = TENPAI_GAIN[tenpaiCount]
			const notenLoss = NOTEN_LOSS[tenpaiCount]
			for (let playerId = 0; playerId < playerCount; ++playerId) {
				let player = round.players[playerId]
				if (player.tenpai) {
					player.gain += tenpaiGain
				} else {
					player.loss += notenLoss
				}
			}
		}
	}

	round.ended = true
	console.log('End round')
	round.end()
	round.render()
}

function playPhase(round) {
	if (round.ended)
		return

	const toPlay = round.toPlay
	switch (round.phase) {
		case PHASE_DRAW:
			{
				// Phase draw
				let tile
				if (round.drawFromWanpai) {
					if (round.wanpai.length <= 2 * maxDora) {
						// Not enough wanpai tiles, round ends
						endRound(round, "四杠散了")
						return
					}
					// Draw a tile from wanpai
					tile = round.wanpai.pop()
					round.drawFromWanpai = undefined
				} else {
					if (!round.pile.length) {
						// Not enough tiles, round ends
						endRound(round)
						return
					}
					// Draw a tile
					tile = round.pile.pop()
				}
				round.drawn = tile
				round.players[toPlay].hand.tiles.push(tile)
				for (let playerId = 0; playerId < playerCount; ++playerId) {
					round.players[playerId].choices = [[CHOICE_NONE]]
				}
				// Check tsumo
				if (hu(round.players[toPlay].hand, true, tile,
					round.roundWind,
					round.players[toPlay].seatWind,
					round.wanpai, round.doraCount)) {
					round.players[toPlay].choices.push([CHOICE_TSUMO])
				}
			}
			break
		case PHASE_CONCEAL:
			{
				// Phase conceal: conceal melds
				for (let playerId = 0; playerId < playerCount; ++playerId) {
					round.players[playerId].choices = [[CHOICE_NONE]]
				}
				for (const meld of checkConcealedMelds(round.players[toPlay].hand.tiles)) {
					// Check concealed melds
					if (meld[0] !== CHOICE_CONCEALED_KONG || round.remainingKan) {
						round.players[toPlay].choices.push(meld)
					}
				}
				for (const meld of checkAddedMelds(round.players[toPlay].hand.tiles, round.players[toPlay].hand.melds)) {
					// Check added melds
					if (meld[0] !== CHOICE_ADDED_KONG || round.remainingKan) {
						round.players[toPlay].choices.push(meld)
					}
				}
				if (!round.players[toPlay].hasMoved && countYaochuu(round.players[toPlay].hand.tiles) >= 9) {
					round.players[toPlay].choices.push([CHOICE_RYUUKYOKU])
				}
			}
			break
		case PHASE_DISCARD:
			{
				// Remove temporary furiten
				for (let suit = 0; suit < 4; ++suit) {
					for (let tile = (suit === 3 ? 31 : (36 | suit)); tile > 3; tile -= 4) {
						round.players[toPlay].hand.furitenTable[tile] &= (~FURITEN_TEMPORARY)
					}
				}

				// Phase discard: discard a tile
				for (let playerId = 0; playerId < playerCount; ++playerId) {
					round.players[playerId].choices = [[CHOICE_NONE]]
				}
				const hand = round.players[toPlay].hand
				if (hand.riichi) {
					round.players[toPlay].choices = [[CHOICE_DISCARD, round.drawn, true]]
				} else {
					round.players[toPlay].choices = []
					let tiles = []
					for (let i = 0; i < hand.tiles.length - 1; ++i) {
						tiles.push(hand.tiles[i])
					}
					let tsumoTile = hand.tiles[hand.tiles.length - 1]
					for (const tile of sortAndUnique(tiles)) {
						round.players[toPlay].choices.push([CHOICE_DISCARD, tile, false])

						let newHand = structuredClone(hand)
						removeElementFromArray(newHand.tiles, tile)
						if (tenpai(newHand)) {
							round.players[toPlay].choices.push([CHOICE_RIICHI, tile, false])
						}
					}
					round.players[toPlay].choices.push([CHOICE_DISCARD, tsumoTile, true])

					let newHand = structuredClone(hand)
					removeElementFromArray(newHand.tiles, tsumoTile)
					if (tenpai(newHand)) {
						round.players[toPlay].choices.push([CHOICE_RIICHI, tsumoTile, true])
					}
				}
			}
			break
		case PHASE_MELD:
			{
				// Phase meld: hu or meld
				round.players[toPlay].choices = [[CHOICE_NONE]]
				const discarded = round.discarded
				const nextPlayer = getNextPlayer(round, toPlay)
				for (let playerId = 0; playerId < playerCount; ++playerId)if (playerId !== toPlay && !round.players[toPlay].hu) {
					let hand = structuredClone(round.players[playerId].hand)
					let choices = [[CHOICE_NONE]]
					if (!hand.riichi) {
						for (const meld of checkMelds(hand.tiles, discarded)) {
							// Check melds
							if ((meld[0] !== CHOICE_CHOW || playerId === nextPlayer) && (meld[0] !== CHOICE_KONG || round.remainingKan)) {
								choices.push(meld)
							}
						}
					}
					// Check hu
					hand.tiles.push(discarded)
					if (hu(hand, false, discarded,
						round.roundWind,
						round.players[playerId].seatWind,
						round.wanpai, round.doraCount)) {
						choices.push([CHOICE_HU])
					}
					round.players[playerId].choices = choices
				}
			}
			break
	}

	for (let playerId = 0; playerId < playerCount; ++playerId)if (round.players[playerId].hu) {
		round.players[playerId].choices = [[CHOICE_NONE]]
	}

	// Auto make choice for players
	for (let playerId = 0; playerId < playerCount; ++playerId) {
		if (round.players[playerId].choices.length === 1) {
			round.players[playerId].choice = round.players[playerId].choices[0]
		} else {
			round.players[playerId].choice = undefined
		}
	}

	let needAI = false
	for (let playerId = 0; playerId < playerCount; ++playerId) {
		if (!playing[playerId] && round.players[playerId].choices.length > 1) {
			needAI = true
		}
	}
	if (needAI) {
		round.render()
		setTimeout(() => {
			for (let playerId = 0; playerId < playerCount; ++playerId) {
				if (!playing[playerId] && round.players[playerId].choices.length > 1) {
					round.players[playerId].choice = aiChoice(round, playerId)
				}
			}
			makeChoice(round)
		}, AI_DELAY)
	} else {
		makeChoice(round)
	}
}

function handleHu(round, hs, tile) {
	for (const h of hs) {
		const u = hu(h[2], h[1], tile,
			round.roundWind,
			round.players[h[0]].seatWind,
			round.wanpai, round.doraCount)
		round.players[h[0]].hu = u
		if (h[1]) {
			for (let playerId = 0; playerId < playerCount; ++playerId)if (playerId !== h[0]) {
				const p = u.points * ((h[0] === round.dealer || playerId === round.dealer) ? 0.5 : 0.25)
				round.players[h[0]].gain += p
				round.players[playerId].loss += p
				round.players[playerId].dealIn.push([p, true])
			}
		} else {
			const p = u.points * ((h[0] === round.dealer) ? 1.5 : 1)
			round.players[h[0]].gain += p
			round.players[h[3]].loss += p
			round.players[h[3]].dealIn.push([p, false])
		}
	}
	let huCount = 0
	for (const player of round.players) {
		if (player.hu) {
			++huCount
		}
	}
	if (huCount < maxHu) {
		round.toPlay = getNextPlayer(round, round.toPlay)
		round.phase = PHASE_DRAW
		playPhase(round)
	} else {
		endRound(round)
	}
}

function makeChoice(round) {
	// Check if all players have made their choices
	{
		for (let playerId = 0; playerId < playerCount; ++playerId) {
			if (!round.players[playerId].choice) {
				round.render()
				return
			}
		}
	}

	const toPlay = round.toPlay
	switch (round.phase) {
		case PHASE_DRAW:
			{
				// Add tsumo
				if (round.players[toPlay].choice[0] === CHOICE_TSUMO) {
					handleHu(round, [[toPlay, true, round.players[toPlay].hand]], round.drawn)
					return
				}
				round.phase = PHASE_CONCEAL
			}
			break
		case PHASE_CONCEAL:
			{
				// Add concealed/added kongs
				if (round.players[toPlay].choice[0] !== CHOICE_NONE) {
					const choice = round.players[toPlay].choice
					if (choice[0] === CHOICE_RYUUKYOKU) {
						endRound(round, "九种九牌")
						return
					} else {
						round.suufonCount = -1
						round.players[toPlay].hasMoved = true
						// Has meld
						for (const tile of choice[3]) {
							removeElementFromArray(round.players[toPlay].hand.tiles, tile)
						}
						if (choice[0] === CHOICE_ADDED_KONG) {
							for (let meld of round.players[toPlay].hand.melds) if (meld[1] === choice[1]) {
								meld[0] = CHOICE_ADDED_KONG
								for (const tile of choice[3]) {
									meld[3].push(tile)
								}
								break
							}
						} else {
							round.players[toPlay].hand.melds.push(structuredClone(choice))
						}
						// Add dora
						if ((choice[0] === CHOICE_ADDED_KONG || choice[0] === CHOICE_CONCEALED_KONG) && round.doraCount < maxDora) {
							++round.doraCount
						}
						// Reduce remaining kan
						if (choice[0] === CHOICE_ADDED_KONG || choice[0] === CHOICE_CONCEALED_KONG) {
							--round.remainingKan
							round.drawFromWanpai = true
						}
						round.phase = PHASE_DRAW
					}
				} else {
					round.phase = PHASE_DISCARD
				}
			}
			break
		case PHASE_DISCARD:
			{
				// Add discard
				const riichi = (round.players[toPlay].choice[0] === CHOICE_RIICHI)
				const discarded = round.players[toPlay].choice[1]
				const tsumogiri = round.players[toPlay].choice[2]
				round.players[toPlay].hand.furitenTable[getTileValue(discarded)] |= FURITEN_PERMANENT
				round.discarded = discarded
				if (round.suufonCount !== -1) {
					if (round.suufonCount) {
						if (round.suufonTile === discarded) {
							++round.suufonCount
							if (round.suufonCount >= 4) {
								endRound(round, "四风连打")
								return
							}
						} else {
							round.suufonCount = -1
						}
					} else {
						if (discarded === TILE_Z1 || discarded === TILE_Z2 || discarded === TILE_Z3 || discarded === TILE_Z4) {
							round.suufonCount = 1
							round.suufonTile = discarded
						} else {
							round.suufonCount = -1
						}
					}
				}
				if (riichi) {
					round.players[toPlay].hand.riichi = true
					round.players[toPlay].riichiIndex = round.players[toPlay].discard.length

					let riichiCount = 0
					for (let playerId = 0; playerId < playerCount; ++playerId)if (round.players[playerId].hand.riichi) {
						++riichiCount
					}
					if (riichiCount >= 4) {
						endRound(round, "四家立直")
						return
					}
				}
				round.players[toPlay].hasMoved = true
				round.players[toPlay].discard.push([discarded, tsumogiri])
				let tiles = round.players[toPlay].hand.tiles
				removeElementFromArray(tiles, discarded)
				tiles.sort(compareTiles)
				if (tenpai(round.players[toPlay].hand)) {
					round.players[toPlay].tenpaiHint = true
				} else {
					round.players[toPlay].tenpaiHint = false
				}
				round.phase = PHASE_MELD
			}
			break
		case PHASE_MELD:
			{
				// Add hu or meld
				if (round.players.findIndex(player => player.choice[0] === CHOICE_HU) !== -1) {
					// Has hu
					let hs = []
					for (let playerId = 0; playerId < playerCount; ++playerId)if (round.players[playerId].choice[0] === CHOICE_HU) {
						round.players[playerId].hand.tiles.push(round.discarded)
						hs.push([playerId, false, round.players[playerId].hand, toPlay])
					}
					if (hs.length >= 3) {
						endRound(round, "三家和了")
					} else {
						handleHu(round, hs, round.discarded)
					}
					return
				} else {
					// No hu
					let choiceType = CHOICE_NONE, toMeld
					for (let playerId = getNextPlayer(round, toPlay); playerId !== toPlay; playerId = getNextPlayer(round, playerId)) {
						if (round.players[playerId].choice[0] > choiceType) {
							choiceType = round.players[playerId].choice[0]
							toMeld = playerId
						}
					}
					for (let playerId = getNextPlayer(round, toPlay); playerId !== toPlay; playerId = getNextPlayer(round, playerId)) {
						if (round.players[playerId].hand.riichi) {
							round.players[playerId].hand.furitenTable[getTileValue(round.discarded)] |= FURITEN_PERMANENT
						} else {
							round.players[playerId].hand.furitenTable[getTileValue(round.discarded)] |= FURITEN_TEMPORARY
						}
					}
					if (choiceType !== CHOICE_NONE) {
						// No suufon renda
						round.suufonCount = -1
						// Has meld
						const choice = round.players[toMeld].choice
						for (const tile of choice[3]) {
							removeElementFromArray(round.players[toMeld].hand.tiles, tile)
						}
						round.players[toMeld].hand.melds.push(structuredClone(choice))

						round.players[toPlay].discard.pop()

						round.toPlay = toMeld
						round.phase = (choiceType === CHOICE_KONG) ? PHASE_DRAW : PHASE_CONCEAL
						// Add dora
						if (choiceType === CHOICE_KONG && round.doraCount < maxDora) {
							++round.doraCount
						}
						// Reduce remaining kan
						if (choiceType === CHOICE_KONG) {
							--round.remainingKan
							round.drawFromWanpai = true
						}
					} else {
						// No hu or meld
						round.toPlay = getNextPlayer(round, round.toPlay)
						round.phase = PHASE_DRAW
					}
				}
			}
			break
	}
	playPhase(round)
}

function setChoice(round, playerId, choice) {
	round.players[playerId].choice = choice
	makeChoice(round)
}